package com.intelij.adapter.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class ClassUtil {


    private static final Logger logger = LoggerFactory.getLogger(ClassUtil.class);

    public static void test(Class cls) {

    }
    /**
     * 加载指定包下的类
     * @param packageName 指定加载哪个包下的类
     * @param getClassLoad 指定类加载器
     * @param isAdd 对包下的类进行过滤
     * @return
     */
    public static final List<Class> getClassList(String packageName, Supplier<ClassLoader> getClassLoad, Function<Class, Boolean> isAdd) {
        List<Class> classList = new ArrayList();
        try {
            ClassLoader classLoader=getClassLoad.get();
            Enumeration<URL> urls =  classLoader.getResources(packageName.replace(".", "/"));
            // 遍历 URL 资源
            while (urls.hasMoreElements()) {
                classList.addAll(urlResolve(urls.nextElement(),packageName,isAdd,classLoader));
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("获取类出错！", e);
        }
        return classList;
    }

    private static List<Class> urlResolve(URL url,String packageName,Function<Class,Boolean> isAdd,ClassLoader classLoader) throws IOException {
        List<Class> classList = new ArrayList();
        if (url != null) {
            // 获取协议名（分为 file 与 jar）
            String protocol = url.getProtocol();
            if (protocol.equals("file")) {
                // 若在 class 目录中，则执行添加类操作
                String packagePath = url.getPath().replaceAll("%20", " ");
                addClass(classList, packagePath, packageName,isAdd,classLoader);
            } else if (protocol.equals("jar")) {
                // 若在 jar 包中，则解析 jar 包中的 entry
                JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                JarFile jarFile = jarURLConnection.getJarFile();
                Enumeration<JarEntry> jarEntries = jarFile.entries();
                while (jarEntries.hasMoreElements()) {
                    JarEntry jarEntry = jarEntries.nextElement();
                    String jarEntryName = jarEntry.getName();
                    // 判断该 entry 是否为 class
                    if (jarEntryName.endsWith(".class")) {
                        // 获取类名
                        String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                        // 执行添加类操作
                        doAddClass(classList, className,isAdd,classLoader);
                    }
                }
            }
        }
        return classList;
    }

    private static void addClass(List<Class> classList, String packagePath, String packageName,Function<Class,Boolean> isAdd,ClassLoader classLoader) {
        try {
            // 获取包名路径下的 class 文件或目录
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
                }
            });
            // 遍历文件或目录
            for (File file : files) {
                String fileName = file.getName();
                // 判断是否为文件或目录
                if (file.isFile()) {
                    // 获取类名
                    String className = fileName.substring(0, fileName.lastIndexOf("."));
                    if (StringUtils.isNotEmpty(packageName)) {
                        className = packageName + "." + className;
                    }
                    // 执行添加类操作
                    doAddClass(classList, className,isAdd,classLoader);
                } else {
                    // 获取子包
                    String subPackagePath = fileName;
                    if (StringUtils.isNotEmpty(packagePath)) {
                        subPackagePath = packagePath + "/" + subPackagePath;
                    }
                    // 子包名
                    String subPackageName = fileName;
                    if (StringUtils.isNotEmpty(packageName)) {
                        subPackageName = packageName + "." + subPackageName;
                    }
                    // 递归调用
                    addClass(classList, subPackagePath, subPackageName,isAdd,classLoader);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.error("添加类出错！", e);
        }
    }

    private static void doAddClass(List<Class> classList, String className,Function<Class,Boolean> isAdd,ClassLoader classLoader) {
        // 加载类
        Class<?> cls;
        try {
            cls = Class.forName(className,false,classLoader);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            logger.error("加载类出错！", e);
            throw new RuntimeException(e);
        }
        // 判断是否可以添加类
        if (isAdd.apply(cls)) {
            // 添加类
            classList.add(cls);
        }
    }
}
